#include "LogStream.h"

namespace OCPP {
    const char digits[] = "9876543210123456789";
    const char* zero = digits + 9;
    const char digitsHex[] = "0123456789ABCDEF";

    // Efficient Integer to String Conversions, by Matthew Wilson.
    template <typename T>
    size_t convert(char buf[], T value)
    {
        T i = value;
        char* p = buf;

        do
        {
            int lsd = static_cast<int>(i % 10);
            i /= 10;
            *p++ = zero[lsd];
        } while (i != 0);

        if (value < 0)
        {
            *p++ = '-';
        }
        *p = '\0';
        std::reverse(buf, p);

        return p - buf;
    }


    size_t convertHex(char buf[], uintptr_t value)
    {
        uintptr_t i = value;
        char* p = buf;

        do
        {
            int lsd = static_cast<int>(i % 16);
            i /= 16;
            *p++ = digitsHex[lsd];
        } while (i != 0);

        *p = '\0';
        std::reverse(buf, p);

        return p - buf;
    }

    template <typename T>
    void LogStream::formatInteger(T v)
    {
        constexpr static int kMaxNumericSize = std::numeric_limits<T>::digits10 + 4;
        if (exBuffer_.empty())
        {
            if (buffer_.avail() >= kMaxNumericSize)
            {
                size_t len = convert(buffer_.current(), v);
                buffer_.add(len);
                return;
            }
            else
            {
                exBuffer_.append(buffer_.data(), buffer_.length());
            }
        }
        auto oldLen = exBuffer_.length();
        exBuffer_.resize(oldLen + kMaxNumericSize);
        size_t len = convert(&exBuffer_[oldLen], v);
        exBuffer_.resize(oldLen + len);
    }

    LogStream& LogStream::operator<<(short v)
    {
        *this << static_cast<int>(v);
        return *this;
    }

    LogStream& LogStream::operator<<(unsigned short v)
    {
        *this << static_cast<unsigned int>(v);
        return *this;
    }

    LogStream& LogStream::operator<<(int v)
    {
        formatInteger(v);
        return *this;
    }

    LogStream& LogStream::operator<<(unsigned int v)
    {
        formatInteger(v);
        return *this;
    }

    LogStream& LogStream::operator<<(long v)
    {
        formatInteger(v);
        return *this;
    }

    LogStream& LogStream::operator<<(unsigned long v)
    {
        formatInteger(v);
        return *this;
    }

    LogStream& LogStream::operator<<(const long long& v)
    {
        formatInteger(v);
        return *this;
    }

    LogStream& LogStream::operator<<(const unsigned long long& v)
    {
        formatInteger(v);
        return *this;
    }

    // TODO: replace this with Grisu3 by Florian Loitsch.
    LogStream& LogStream::operator<<(const double& v)
    {
        constexpr static int kMaxNumericSize = 32;
        if (exBuffer_.empty())
        {
            if (buffer_.avail() >= kMaxNumericSize)
            {
                int len = snprintf(buffer_.current(), kMaxNumericSize, "%.12g", v);
                buffer_.add(len);
                return *this;
            }
            else
            {
                exBuffer_.append(buffer_.data(), buffer_.length());
            }
        }
        auto oldLen = exBuffer_.length();
        exBuffer_.resize(oldLen + kMaxNumericSize);
        int len = snprintf(&(exBuffer_[oldLen]), kMaxNumericSize, "%.12g", v);
        exBuffer_.resize(oldLen + len);
        return *this;
    }

    LogStream& LogStream::operator<<(const long double& v)
    {
        constexpr static int kMaxNumericSize = 48;
        if (exBuffer_.empty())
        {
            if (buffer_.avail() >= kMaxNumericSize)
            {
                int len = snprintf(buffer_.current(), kMaxNumericSize, "%.12Lg", v);
                buffer_.add(len);
                return *this;
            }
            else
            {
                exBuffer_.append(buffer_.data(), buffer_.length());
            }
        }
        auto oldLen = exBuffer_.length();
        exBuffer_.resize(oldLen + kMaxNumericSize);
        int len = snprintf(&(exBuffer_[oldLen]), kMaxNumericSize, "%.12Lg", v);
        exBuffer_.resize(oldLen + len);
        return *this;
    }

    LogStream& LogStream::operator<<(const void* p)
    {
        uintptr_t v = reinterpret_cast<uintptr_t>(p);
        constexpr static int kMaxNumericSize =
            std::numeric_limits<uintptr_t>::digits / 4 + 4;
        if (exBuffer_.empty())
        {
            if (buffer_.avail() >= kMaxNumericSize)
            {
                char* buf = buffer_.current();
                buf[0] = '0';
                buf[1] = 'x';
                size_t len = convertHex(buf + 2, v);
                buffer_.add(len + 2);
                return *this;
            }
            else
            {
                exBuffer_.append(buffer_.data(), buffer_.length());
            }
        }
        auto oldLen = exBuffer_.length();
        exBuffer_.resize(oldLen + kMaxNumericSize);
        char* buf = &exBuffer_[oldLen];
        buf[0] = '0';
        buf[1] = 'x';
        size_t len = convertHex(buf + 2, v);
        exBuffer_.resize(oldLen + len + 2);
        return *this;
    }

    const char* LogStream::bufferData() const
    {
        if (!exBuffer_.empty())
        {
            return exBuffer_.data();
        }
        return buffer_.data();
    }

    size_t LogStream::bufferLength() const
    {
        if (!exBuffer_.empty())
        {
            return exBuffer_.length();
        }
        return buffer_.length();
    }
    void LogStream::resetBuffer()
    {
        exBuffer_.clear();
    }

	void LogStream::append(const char* data, size_t len)
	{
        if (exBuffer_.empty())
        {
            if (!buffer_.append(data, len))
            {
                exBuffer_.append(buffer_.data(), buffer_.length());
                exBuffer_.append(data, len);
            }
        }
        else
        {
            exBuffer_.append(data, len);
        }
	}
}